![Spring WebFluxのREST APIにAmazon CodeGuru Profilerを設定してみた](https://devio2023-media.developers.io/wp-content/uploads/2019/11/aws-eyecatch.png)
Spring WebFluxのREST APIにAmazon CodeGuru Profilerを設定してみた
Spring WebFluxを使ってKotlinコルーチンでReactiveなREST APIのサンプルアプリを作成し、Amazon CodeGuru Profilerを設定してみました。
結論を言うと、プロダクション環境ではないので、あまり有効なプロファイルは取得できませんでした。
ただ、比較的簡単に設定できることは確認できたので、機会があればプロダクション環境で適用してみたいと思います。
アジェンダ
- Spring WebFlux REST APIサンプルアプリの概要
- Amazon CodeGuru Profilerの設定方法
- 取得したプロファイル結果
Spring WebFlux REST APIサンプルアプリの概要
サンプルといえばTODOアプリ、簡単なCRUD REST APIをDDDっぽく作成してみました。
DBはPostgreSQLで、ユーザー認証は別サービスをHTTPで叩くイメージです。
概要のみ簡単に記載しますが、詳細については、githubを参照してください。
ファイル構成はこんな感じです。
. ├── App.kt ├── Beans.kt ├── api │ ├── Router.kt │ ├── exception │ │ ├── ErrorResponse.kt │ │ └── ExceptionHandler.kt │ ├── filters │ │ └── AuthFilter.kt │ └── handlers │ └── TodoHandler.kt ├── application │ ├── Exceptions.kt │ ├── auth │ │ └── AuthService.kt │ └── todo │ ├── TodoData.kt │ └── TodoService.kt ├── domain │ └── model │ ├── auth │ │ ├── AuthToken.kt │ │ ├── User.kt │ │ ├── UserId.kt │ │ └── UserRepository.kt │ └── todo │ ├── Todo.kt │ ├── TodoContent.kt │ ├── TodoId.kt │ ├── TodoRepository.kt │ └── TodoTitle.kt └── infrastructure ├── db │ └── todo │ ├── PostgreSQLClient.kt │ └── PostgreSQLTodoRepository.kt └── http └── auth └── AuthHttpClient.kt
ルーティングはアノテーション使う方法もありますが、Router Functionを使うと一箇所にまとめられます。コルーチンなので、coRouter使用します。
class Router(private val todoHandler: TodoHandler, private val authFilter: AuthFilter) { fun router() = coRouter { GET("/todo", todoHandler::list) GET("/todo/{id}", todoHandler::find) POST("/todo", todoHandler::create) PATCH("/todo/{id}", todoHandler::update) DELETE("/todo/{id}", todoHandler::delete) }.filter(authFilter) }
フィルターでAuthorizationヘッダの認証をしています。全体をフィルタする場合はWebFilterを使うのが一般的だと思いますが、Router Funcitonに設定するタイプのHandlerFilterFunctionを使用してみました。
class AuthFilter(private val service: AuthService) : HandlerFilterFunction<ServerResponse, ServerResponse> { override fun filter(request: ServerRequest, next: HandlerFunction<ServerResponse>): Mono<ServerResponse> { val token = tokenOf(request) return mono(Dispatchers.Unconfined) { val user = service.authenticate(token) request.attributes()["AUTHENTICATED_USER"] = user request }.flatMap(next::handle) } private fun tokenOf(request: ServerRequest): AuthToken = AuthToken.createOrNull(request.headers().firstHeader("Authorization")) ?: throw TodoUnauthorizedException("Auth header is required.") }
リポジトリはSQLを直接実行。
class PostgreSQLTodoRepository(private val client: DatabaseClient) : TodoRepository { override suspend fun fetchAll(userId: UserId): List<Todo> = client .execute("SELECT * FROM todos WHERE user_id = :userId") .bind("userId", userId.value) .map(::readRow) .all() .collectList() .awaitSingle() override suspend fun find(userId: UserId, todoId: TodoId): Todo? = client .execute("SELECT * FROM todos WHERE id = :todoId AND user_id = :userId") .bind("userId", userId.value) .bind("todoId", todoId.value) .map(::readRow) .awaitFirstOrNull() override suspend fun insert(userId: UserId, title: TodoTitle, content: TodoContent): Todo = client .execute("INSERT INTO todos (user_id, title, content) VALUES(:userId, :title, :content) RETURNING *") .bind("userId", userId.value) .bind("title", title.value) .bind("content", content.value) .map(::readRow) .awaitFirst() // 以下省略 }
ツッコミどころはあると思うのですが、サンプルなので。
(本当は、Amazon CodeGuru Reviewer にチェックしてもらいたかったのですが、JavaじゃなくてKotlinだったので、一行も認識されませんでした?)
Amazon CodeGuru Profilerの設定方法
1. プロファイリンググループを作成します
コントロールパネル、CodeGuru、プロファイリンググループから、任意の名前のプロファイリンググループを作成します。
2. アプリ実行ロールにプロファイル用のポリシーをアタッチします
ドキュメントを参考に、アプリ実行ロールの以下のポリシーをアタッチします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "codeguru-profiler:ConfigureAgent", "codeguru-profiler:PostAgentProfile" ], "Resource": "arn:aws:codeguru-profiler:<region>:<accountID>:profilingGroup/<profilingGroupName>" } ] }
3. アプリにプロファイル起動処理を追加します
build.gradle.tksに以下を追加します。
repositories { maven { url = uri("https://d1osg35nybn3tt.cloudfront.net") } } dependencies { implementation("com.amazonaws:codeguru-profiler-java-agent:1.0.1") }
アプリのエントリーエントリーポイントに以下を追加します。
fun main(args: Array<String>) { Profiler.builder() .profilingGroupName("Test-Reactive-Web") .build() .start() // 以下省略 }
設定は以上になります。
取得したプロファイル結果
アプリを起動し、負荷をかけてプロファイルを取得してみました。 ほとんどWebFluxのチャンネル関係の処理でしたが、フレームを非表示にすることで、自作部分も出力されていることがわかりました。
CPU
レイテンシー
まとめ
Amazon CodeGuru Profilerを設定してみました。
JVMで動いているアプリケーションであれば、設定はとても楽だと思います。別のエージェントサービスを立ち上げる必要もないので、コンテナにもすんなり適用できると思います。
機会があれば、プロダクション環境に適用し、アプリのパフォーマンス改善に利用してみたいと思いました。